﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Linq;
using Less.Core;
using Less.Core.Model;
using Less.Tools;

namespace Less
{
    public static class Status
    {
        private static readonly object TokenGetApplicationStatus = new object();
        private static readonly object TokenGetYesterdayApplicationStatus = new object();
        private static readonly object TokenAddMessage = new object();
        private static readonly object TokenCounter = new object();
        private static bool _loaded;
        private static Queue<LogEntry> _buffer;
        private static ApplicationStatus _applicationStatus;
        private static ApplicationStatus _yesterdayApplicationStatus;
        private static DatabaseStatus _databaseStatus;

        
        public static ApplicationStatus ApplicationStatus
        {
            get
            {
                lock (TokenGetApplicationStatus)
                {
                    if (_applicationStatus == null)
                    {
                        try
                        {
                            LoadStatus();
                        }
                        catch (Exception e)
                        {
                            Trace.TraceError(e.Message);
                            SaveStatus();
                            throw ;
                        }
                    }
                }
                return _applicationStatus;
            }
        }
        public static ApplicationStatus YesterdayApplicationStatus
        {
            get
            {
                lock (TokenGetYesterdayApplicationStatus)
                {
                    if (_yesterdayApplicationStatus == null)
                    {
                        string path = GetFilePath(DateTime.Now.AddDays(-1));
                        if (File.Exists(path))
                        {
                            _yesterdayApplicationStatus = Functions.Deserialize<ApplicationStatus>(path);
                        }
                        else
                        {
                            _yesterdayApplicationStatus = new ApplicationStatus();
                        }
                    }
                }
                return _yesterdayApplicationStatus;
            }
        }

        public static void LoadStatus()
        {
            string path = GetFilePath(DateTime.Now);
            if (File.Exists(path))
            {
                _applicationStatus = Functions.Deserialize<ApplicationStatus>(path);
            }
            else
            {
                _applicationStatus = new ApplicationStatus();
                _applicationStatus.Created = DateTime.Now;
                _applicationStatus.Messages = new List<string>();
            }
            _applicationStatus.Loaded = DateTime.Now;
            lock (TokenAddMessage)
            {
                if (_buffer != null)
                {
                    while (_buffer.Count > 0)
                    {
                        LogEntry item = _buffer.Dequeue();
                        UpdateApplicationStatus(item);
                    }
                }
                SaveStatus();
                LoadDatabaseStatus();
                _loaded = true;
            }
        }
        public static void LoadDatabaseStatus()
        {
            string path = GetDatabaseFilePath();
            if ( File.Exists(path) )
            {
                _databaseStatus = Functions.Deserialize<DatabaseStatus>(path);
            }
            else
            {
                _databaseStatus = new DatabaseStatus();
                _databaseStatus.Counter = 0;
                _databaseStatus.ScriptsExecuted = new List<string>();
                SaveDatabaseStatus();
            }
        }

        public static void AddMessage(LogEntry logEntry)
        {
            lock (TokenAddMessage)
            {
                if (_loaded)
                {
                    UpdateApplicationStatus(logEntry);
                    SaveStatus();
                }
                else
                {
                    if (_buffer == null)
                    {
                        _buffer = new Queue<LogEntry>();
                    }
                    _buffer.Enqueue(logEntry);
                }
            }
        }
        private static void UpdateApplicationStatus(LogEntry logEntry)
        {
            if (logEntry.Severity >= EntrySeverities.Error)
            {
                _applicationStatus.NumCoreErrors++;
            }
            if (_applicationStatus.Messages == null) _applicationStatus.Messages = new List<string>();
            _applicationStatus.Messages.Add(logEntry.Message);
        }

        public static void SaveStatus()
        {
            GetStats();
            _applicationStatus.Serialize(GetFilePath(DateTime.Now));
        }

        public static void SaveDatabaseStatus()
        {
            _databaseStatus.Serialize(GetDatabaseFilePath());
        }

        private static void GetStats()
        {
            try
            {
                _applicationStatus.WorkDirectory = Config.WorkDirectory;
                _applicationStatus.AppDirectory = Config.AppDirectory;
                _applicationStatus.Loggers = Log.Loggers.Select(l => l.GetType().Name).ToList();
                _applicationStatus.MemorySize = GC.GetTotalMemory(false);
                // TODO: Collect system metrics
            }
            catch (Exception e)
            {
                Log.Error(e);
            }
        }

        private static string GetApplicationName()
        {
            string name= ConfigurationManager.AppSettings["Config.ApplicationName"];
            if (name == null) name = "lessApp";
            return name;
        }
        private static string GetWorkDirectory()
        {
            string name = ConfigurationManager.AppSettings["Config.WorkDirectory"];
            name = string.Format(name, System.Environment.UserName);
            if (name == null) name = Environment.GetFolderPath(folder: Environment.SpecialFolder.MyDocuments) + "\\Work\\";
            return name;
        }
        private static string GetFilePath(DateTime date)
        {
            string dir = GetWorkDirectory() + "Data\\Logs\\" + GetApplicationName() + "_" + date.ToString("yyyyMMdd") + ".js";
            return dir;
        }

        private static string GetDatabaseFilePath()
        {
            string dir = GetWorkDirectory() + "Data\\SQL\\" + GetApplicationName()  + "Database.js";
            return dir;
        }

        internal static void AddExecutedScript(string script)
        {
            if ( _databaseStatus.ScriptsExecuted == null ) _databaseStatus.ScriptsExecuted = new List<string>();
            _databaseStatus.ScriptsExecuted.Add(script);
            SaveDatabaseStatus();
        }

        internal static bool ScriptNotExecuted(string script)
        {
            if ( _databaseStatus.ScriptsExecuted == null ) return true;
            else
                return _databaseStatus.ScriptsExecuted.Any(s => s == script) == false;
        }

        public static long GetNextCounter()
        {
            lock ( TokenCounter )
            {
                _databaseStatus.Counter++;
                SaveDatabaseStatus();
                return _databaseStatus.Counter;
            }
        }
    }

    public class ApplicationStatus
    {
        public DateTime Created { get; set; }

        public DateTime Loaded { get; set; }

        public string AppDirectory { get; set; }

        public string WorkDirectory { get; set; }

        public List<String> Loggers { get; set; }

        public int NumCoreErrors { get; set; }

        public List<String> Messages { get; set; }

        public long SqlSize { get; set; }

        public long MemorySize { get; set; } 
    }

    public class DatabaseStatus
    {
        public List<String> ScriptsExecuted { get; set; }

        public int Counter { get; set; }
    }
}